package io.mft.commons.db;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.apache.ibatis.session.Configuration;

import cn.hutool.core.util.ObjectUtil;
import io.mft.commons.db.mapper.annotation.BeanMapper;
import io.mft.commons.exception.MFTException;
import io.mft.commons.util.CommonUtil;
public class MFTMyBatisConfigParser {
    /**
     * 用来保存 datasourceId和Mybatis的Configuration对象的
     */
    private static Map<String, Configuration>  MYBATIS_CONFIGS = new HashMap();
    
    /**
     * 存储所有MyBatis配置文件中的信息，以Node节点保存
     */
    private static Map<String, MyBatisSqlInfo> SQLSOURCE_CACHE = new HashMap<String, MyBatisSqlInfo>();
    
    /**
     * 描述：解析xml配置文件
     * 
     * @author 岳知之
     * @created 2016年9月30日 上午1:49:48
     * @since
     * @param sqlXml
     * @param datasource
     * @return
     */
    protected static MyBatisSqlInfo parseMyBatisXml(String sqlXml, String datasource)
    {
        MyBatisSqlInfo batisSqlInfo = null;
        XPathParser parser = new XPathParser(sqlXml, false, null, new XMLMapperEntityResolver());
        List<XNode> xNodeList = parser.evalNodes("select|update|delete|insert");
        if ( xNodeList != null && xNodeList.size() > 0 )
        {
            XNode xNode = xNodeList.get(0);
            batisSqlInfo = parseMapFile(datasource, xNode);
        }
        return batisSqlInfo;
    }
    
    /**
     * 描述：解析xml配置文件
     * 
     * @author 岳知之
     * @created 2016年9月30日 上午1:49:48
     * @since
     * @param sqlXml
     * @param datasource
     * @return
     */
    protected static Map<String, MyBatisSqlInfo> parseMyBatisXmlByFile(InputStream in, String datasource)
    {
        Map<String, MyBatisSqlInfo> batisSqlInfoMap = new HashMap();
        MyBatisSqlInfo batisSqlInfo = null;
        XPathParser parser = new XPathParser(in, false, null, new XMLMapperEntityResolver());
        List<XNode> xNodeList = parser.evalNodes("mapper/select|mapper/update|mapper/delete|mapper/insert|mapper/sql");
        if ( xNodeList != null && xNodeList.size() > 0 )
        {
            MyBatisSqlInfo[] myBatisSqlInfos = new MyBatisSqlInfo[xNodeList.size()];
            
            for (Iterator iterator = xNodeList.iterator(); iterator.hasNext();)
            {
                XNode xNode = (XNode) iterator.next();
                batisSqlInfo = parseMapFile(datasource, xNode);
                // 获取自定义ID
                String id = xNode.getStringAttribute("id");
                if ( ObjectUtil.isNotEmpty(id) )
                {
                    batisSqlInfo.setId(id);
                }
                else
                {
                    throw new RuntimeException("mybatis配置文件id不能为空,节点" + xNode);
                }
                
                batisSqlInfoMap.put(batisSqlInfo.getId(), batisSqlInfo);
            }
        }
        return batisSqlInfoMap;
    }
    
    private static MyBatisSqlInfo parseMapFile(String datasource, XNode xNode)
    {
        MyBatisSqlInfo batisSqlInfo;
        batisSqlInfo = new MyBatisSqlInfo();
        
        // SELECT,INSERT,UPDATE,DELETE
        batisSqlInfo.setSqlType(xNode.getName().toUpperCase());
        batisSqlInfo.setDatasourceId(datasource);
        
        // 是否使用缓存
        batisSqlInfo.setCache(xNode.getBooleanAttribute("useCache", false));
        
        // 添加表名
        String tableStr = xNode.getStringAttribute("tables");
        if ( ObjectUtil.isNotEmpty(tableStr) )
        {
            batisSqlInfo.setTables(tableStr.split(","));
        }
        Configuration configuration = getConfiguration(datasource);
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, xNode);
        batisSqlInfo.setSqlSource(builder.parseScriptNode());
        
        return batisSqlInfo;
    }
   public static class SqlInfo{
        private boolean      isCache       = false;
        private String[]     tables;
        
        private String       datasourceId  = "";   
                                                    
        private String       sql           = "";   
                                                    
        private String       sqlType       = "";   
                                                    
        private int          resultType;           
                                                    
        private int          resultAmount;         
                                                    
        private List<String> paramNameList = null; 
                                                    
        private List<String> valueList     = null; 
                                                    
        public String getSql()
        {
            return sql;
        }
        
        public void setSql(String sql)
        {
            this.sql = sql;
        }
        
        public String getSqlType()
        {
            return sqlType;
        }
        
        public void setSqlType(String sqlType)
        {
            this.sqlType = sqlType;
        }
        
        public int getResultType()
        {
            return resultType;
        }
        
        public void setResultType(int resultType)
        {
            this.resultType = resultType;
        }
        
        public String getDatasourceId()
        {
            return datasourceId;
        }
        
        public void setDatasourceId(String datasourceId)
        {
            this.datasourceId = datasourceId;
        }
        
        public List<String> getParamNameList()
        {
            return paramNameList;
        }
        
        public void setParamNameList(List<String> paramNameList)
        {
            this.paramNameList = paramNameList;
        }
        
        public List<String> getValueList()
        {
            return valueList;
        }
        
        public Object[] getValues()
        {
            return valueList.toArray();
        }
        
        public void setValueList(List<String> valueList)
        {
            this.valueList = valueList;
        }
        
        public boolean isCache()
        {
            return isCache;
        }
        
        public void setCache(boolean isCache)
        {
            this.isCache = isCache;
        }
        
        public String[] getTables()
        {
            return tables;
        }
        
        public void setTables(String[] tables)
        {
            this.tables = tables;
        }
        
        @Override
        public String toString()
        {
            StringBuffer buffer = new StringBuffer();
            buffer.append("sql:").append(sql).append("\r");
            if ( paramNameList != null )
            {
                int i = 0;
                for (String arg : paramNameList)
                {
                    buffer.append("arg" + i).append(":").append(arg);
                }
            }
            
            return buffer.toString();
        }
        
        public int getResultAmount()
        {
            return resultAmount;
        }
        
        public void setResultAmount(int resultAmount)
        {
            this.resultAmount = resultAmount;
        }
        
}
    /**
     * 描述：根据入参装配sql得到SqlInfo
     * 
     * @author 岳知之
     * @created 2016年9月30日 上午12:33:11
     * @since
     * @param param
     *            sql入参
     * @param myBatisSqlInfo
     * @return
     */
    protected static SqlInfo assembleSqlInfo(Map param, MyBatisSqlInfo myBatisSqlInfo)
    {
        SqlInfo sqlInfo = null;
        SqlSource sqlSource = myBatisSqlInfo.getSqlSource();
        Iterator<Map.Entry<String, Object>> it = param.entrySet().iterator();
        Map<String, Object> map = new HashMap<String, Object>();
        while (it.hasNext()){
        	Map.Entry<String, Object> entries = it.next();
        	if (null != entries.getValue() && entries.getValue().getClass().isAnnotationPresent(BeanMapper.class)){
        		Map<String, Object> data = new HashMap<String, Object>();
        		try {
        			data = CommonUtil.objectToMap(entries.getValue());
				} catch (Exception e) {
					throw new MFTException("", e);
				}
        		for (Map.Entry<String, Object> entry : data.entrySet()){
        			if (null != entry.getValue()){
        				String key = entries.getKey() + "." + entry.getKey();
        				map.put(key, entry.getValue());
        			} else {
        				continue;
        			}
        		}
        		it.remove();
        	}
        }
        param.putAll(map);
        BoundSql boundSql = sqlSource.getBoundSql(param);
        if ( boundSql != null )
        {
            String sql = boundSql.getSql();
            /**
             * 查询入参
             */
            List argsList = new ArrayList();
            /**
             * 入参值
             */
            List valueList = new ArrayList();
            // 遍历导入参数与值
            List<ParameterMapping> paramList = boundSql.getParameterMappings();
            HashMap parameterObject = (HashMap) boundSql.getParameterObject();
            for (ParameterMapping mapping : paramList)
            {
                String property = mapping.getProperty();
                argsList.add(property);
                if ( boundSql.hasAdditionalParameter(property) )
                {
                    Object value = boundSql.getAdditionalParameter(property);
                    valueList.add(value);
                    param.put(property, value);
                }
                else if ( parameterObject != null && parameterObject.containsKey(property) )
                {
                    Object value = parameterObject.get(property);
                    valueList.add(value);
                    param.put(property, value);
                }
                else
                {
                    valueList.add(null);
                }
            }
            sqlInfo = new SqlInfo();
            sqlInfo.setSql(sql);
            sqlInfo.setParamNameList(argsList);
            sqlInfo.setValueList(valueList);
            sqlInfo.setCache(myBatisSqlInfo.isCache());
            sqlInfo.setDatasourceId(myBatisSqlInfo.getDatasourceId());
            sqlInfo.setSqlType(myBatisSqlInfo.getSqlType());
            sqlInfo.setTables(myBatisSqlInfo.getTables());
        }
        
        return sqlInfo;
    }
    
    /**
     * 描述：获取Configuration对象，如果存在datasource，则对databaseId参数赋值，方便处理多种类型的数据库
     * 
     * @author 岳知之
     * @created 2016年9月25日 下午8:40:19
     * @since
     * @param datasource
     * @return
     */
    protected static Configuration getConfiguration(String datasource)
    {
        Configuration configuration = MYBATIS_CONFIGS.get(datasource);
        if ( configuration == null )
        {
            configuration = new Configuration();
            String databaseId = "";
            configuration.setDatabaseId(databaseId);
            MYBATIS_CONFIGS.put(datasource, configuration);
        }
        return configuration;
    }
    
    /**
     * 描述：用来存储mybatis文件解析出来的数据
     * 
     * @author yuezz
     * @created 2016年9月14日 上午10:32:35
     * @since
     */
    protected static class MyBatisSqlInfo
    {
        private SqlSource sqlSource;
        
        private boolean   isCache      = false;
        
        private String[]  tables;
        
        private String    datasourceId = "";   // 数据源ID
                                                
        private String    sqlType      = "";   // SELECT、INSERT、UPDATE
                                                
        private String    id           = "";   // sql id
                                                
        public void setTables(String[] tables)
        {
            for (int i = 0; i < tables.length; i++)
            {
                tables[i] = tables[i].trim();
            }
            this.tables = tables;
        }
        
        public SqlSource getSqlSource()
        {
            return sqlSource;
        }
        
        public void setSqlSource(SqlSource sqlSource)
        {
            this.sqlSource = sqlSource;
        }
        
        public boolean isCache()
        {
            return isCache;
        }
        
        public void setCache(boolean isCache)
        {
            this.isCache = isCache;
        }
        
        public String getDatasourceId()
        {
            return datasourceId;
        }
        
        public void setDatasourceId(String datasourceId)
        {
            this.datasourceId = datasourceId;
        }
        
        public String getSqlType()
        {
            return sqlType;
        }
        
        public void setSqlType(String sqlType)
        {
            this.sqlType = sqlType;
        }
        
        public String[] getTables()
        {
            return tables;
        }
        
        public String getId()
        {
            return id;
        }
        
        public void setId(String id)
        {
            this.id = id;
        }
    }
    
    protected static Map<String, MyBatisSqlInfo> getCache()
    {
        return SQLSOURCE_CACHE;
    }
    
    /**
     * 描述：获取缓存大小
     * 
     * @author yuezz
     * @created 2016年9月14日 上午9:54:43
     * @since
     * @return
     */
    public static int getCacheSize()
    {
        return SQLSOURCE_CACHE.size();
    }
    
    /**
     * 描述：清除SQL缓存
     * 
     * @author yuezz
     * @created 2016年9月14日 上午9:54:25
     * @since
     */
    public static void clear()
    {
        SQLSOURCE_CACHE.clear();
    }
}
